package com.thinkingcoder.tcrpc.core.consumer;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;

import com.thinkingcoder.tcrpc.core.annotation.TcConsumer;
import com.thinkingcoder.tcrpc.core.api.RpcContext;
import com.thinkingcoder.tcrpc.core.meta.InstanceMeta;
import com.thinkingcoder.tcrpc.core.meta.ServiceMeta;
import com.thinkingcoder.tcrpc.core.registry.ChangedListener;
import com.thinkingcoder.tcrpc.core.registry.Event;
import com.thinkingcoder.tcrpc.core.registry.RegistryCenter;
import com.thinkingcoder.tcrpc.core.util.MethodUtils;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * 消费者启动类
 */
@Data
@Slf4j
public class ConsumerBootstrap implements ApplicationContextAware, EnvironmentAware {

    // 实现 ApplicationContextAware 接口后，需要实现 setApplicationContext(ApplicationContext applicationContext) 方法，
    // 在用 lombok 的 @Data 注解后，形参命名为 applicationContext 即可
    ApplicationContext applicationContext;

    Environment environment;

    // 服务消费者的桩点
    private Map<String, Object> stub = new HashMap<>();

    public void start() {
        // 获取注册中心
        RegistryCenter registryCenter = applicationContext.getBean(RegistryCenter.class);

        RpcContext context = applicationContext.getBean(RpcContext.class);

        // 通过Spring拿到所有的bean的名字
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            // 通过 beanName 获取 bean
            Object bean = applicationContext.getBean(name);

            // 获取 bean 中的指定注解的 属性 集合
            List<Field> fields = MethodUtils.findAnnotatedField(bean.getClass(), TcConsumer.class);

            fields.stream().forEach(f -> {
                Class<?> service = f.getType();
                String serviceName = service.getCanonicalName();
                log.info(" ===> " + f.getName());
                try {
                    // 从 服务消费者 桩点中，获取 代理的服务提供者接口，用作服务消费的执行接口
                    Object consumer = stub.get(serviceName);
                    if (consumer == null) {
                        consumer = createFromRegistry(service, context, registryCenter);
                        stub.put(serviceName, consumer);
                    }
                    f.setAccessible(true);
                    f.set(bean, consumer);
                } catch (Exception e) {
                    // ignore and print it
                    log.warn(" ==> Field[{}.{}] create consumer failed.", serviceName, f.getName());
                    log.error("Ignore and print it as: ", e);
                }
            });
        }
    }

    private Object createFromRegistry(Class<?> service, RpcContext context, RegistryCenter registryCenter) {
        ServiceMeta serviceMeta =
            ServiceMeta.builder().app(context.param("app.id")).namespace(context.param("app.namespace"))
                .env(context.param("app.env")).name(service.getCanonicalName()).build();
        final List<InstanceMeta> providers = registryCenter.fetchAll(serviceMeta);

        log.info(" ===> map to providers");
        providers.forEach(System.out::println);

        registryCenter.subscribe(serviceMeta, new ChangedListener() {
            @Override
            public void fire(Event event) {
                // 在执行监听事件后，怎么影响 handler 里面的 List<String> providers 的值呢？
                // 其实就是 providers 使用相同的内存地址 ***，主要不要仅仅接受值使用，而放弃相同的引用地址；
                providers.clear();
                providers.addAll(event.getData());
            }
        });

        return createConsumer(service, context, providers);
    }

    // 生成 服务提供者 接口的，代理执行类
    private Object createConsumer(Class<?> service, RpcContext context, List<InstanceMeta> providers) {
        return Proxy.newProxyInstance(service.getClassLoader(), new Class[] {service},
            new TCInvocationHandler(service, context, providers));
    }

}
